package com.quanyan.pay.api.alipay.service.impl;

import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.internal.util.AlipaySignature;
import com.alipay.api.request.*;
import com.alipay.api.response.*;
import com.quanyan.common.utils.GsonUtils;
import com.quanyan.common.utils.TimeUtils;
import com.quanyan.pay.exception.AliException;
import com.quanyan.pay.api.alipay.service.AlipayAPIService;
import com.quanyan.pay.api.alipay.vo.*;
import com.quanyan.pay.entity.AlipayConfigAccount;
import com.quanyan.pay.enums.AliTradeTypeEnum;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVRecord;
import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import java.io.*;
import java.math.BigDecimal;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;

/**
 * Created by Zheng.Ke
 * Date 2017/3/15.
 */
@Service("alipayAPIService")
public class AlipayAPIServiceImpl implements AlipayAPIService {
    private static Logger logger = LoggerFactory.getLogger(AlipayAPIServiceImpl.class);
    private static Map<String, AlipayClient> appIdToAliPayAccountMap = new HashMap<>();

    @Override
    public AliTradeWapPayRespVo tradeWapPay(AliTradeWapPayReqVo vo) {
        AlipayConfigAccount alipayConfigAccount = vo.getAlipayConfigAccount();
        AlipayClient alipayClient = initAlipayClient(alipayConfigAccount);

        AlipayTradeWapPayRequest alipayRequest = new AlipayTradeWapPayRequest();
        // 不需要传过去
        vo.setAlipayConfigAccount(null);
        // 填充业务参数
        alipayRequest.setBizContent(GsonUtils.toJson(vo));
        alipayRequest.setNotifyUrl(alipayConfigAccount.getNotifyUrl());

        AliTradeWapPayRespVo respVo = new AliTradeWapPayRespVo();
        String formStr; //调用SDK生成表单
        try {
            formStr = alipayClient.pageExecute(alipayRequest).getBody();
        } catch (AlipayApiException e) {
            logger.error("支付宝手机网站支付接口调用出错。请求参数：" + GsonUtils.toJson(vo) + "。", e);
            respVo.setRet(false);
            return respVo;
        }
        respVo.setFormStr(formStr);

        logger.info("访问支付宝接口【手机网站支付接口】。请求参数：{}，晌应：{}。", GsonUtils.toJson(vo), GsonUtils.toJson(respVo));
        return respVo;
    }

    @Override
    public AlipayTradeQueryResponse tradeQuery(AliTradeQueryReqVo vo) {
        AlipayConfigAccount alipayConfigAccount = vo.getAlipayConfigAccount();
        AlipayClient alipayClient = initAlipayClient(alipayConfigAccount);

        AlipayTradeQueryRequest  alipayRequest = new AlipayTradeQueryRequest();
        // 不需要传过去
        vo.setAlipayConfigAccount(null);
        // 填充业务参数
        alipayRequest.setBizContent(GsonUtils.toJson(vo));
        AlipayTradeQueryResponse response;
        try {
            response = alipayClient.execute(alipayRequest);
        } catch (AlipayApiException e) {
            logger.error("支付宝交易查询接口调用出错。请求参数：" + GsonUtils.toJson(vo) + "。", e);
            throw new AliException("支付宝交易查询接口调用出错。请求参数：" + GsonUtils.toJson(vo) + "。", e);
        }

        return response;
    }

    @Override
    public AlipayTradeCloseResponse tradeClose(AliTradeCloseReqVo vo) {
        AlipayConfigAccount alipayConfigAccount = vo.getAlipayConfigAccount();
        AlipayClient alipayClient = initAlipayClient(alipayConfigAccount);

        AlipayTradeCloseRequest alipayRequest = new AlipayTradeCloseRequest();
        // 不需要传过去
        vo.setAlipayConfigAccount(null);
        // 填充业务参数
        alipayRequest.setBizContent(GsonUtils.toJson(vo));
        AlipayTradeCloseResponse response;
        try {
            response = alipayClient.execute(alipayRequest);
        } catch (AlipayApiException e) {
            logger.error("支付宝交易关闭接口调用出错。请求参数：" + GsonUtils.toJson(vo) + "。", e);
            throw new AliException("支付宝交易关闭接口调用出错。请求参数：" + GsonUtils.toJson(vo) + "。", e);
        }

        logger.info("访问支付宝接口【交易关闭接口】。请求参数：{}，晌应：{}。", GsonUtils.toJson(vo), GsonUtils.toJson(response));
        return response;
    }

    @Override
    public AlipayTradePayResponse tradePay(AliTradePayReqVo vo) {
        AlipayConfigAccount alipayConfigAccount = vo.getAlipayConfigAccount();
        AlipayClient alipayClient = initAlipayClient(alipayConfigAccount);

        AlipayTradePayRequest alipayRequest = new AlipayTradePayRequest();
        // 不需要传过去
        vo.setAlipayConfigAccount(null);
        // 填充业务参数
        alipayRequest.setBizContent(GsonUtils.toJson(vo));
        AlipayTradePayResponse response;
        try {
            response = alipayClient.execute(alipayRequest);
        } catch (AlipayApiException e) {
            logger.error("支付宝条码支付接口调用出错。请求参数：" + GsonUtils.toJson(vo) + "。", e);
            throw new AliException("支付宝条码支付接口调用出错。请求参数：" + GsonUtils.toJson(vo) + "。", e);
        }

        logger.info("访问支付宝接口【统一收单交易支付接口（条码支付）】。请求参数：{}，晌应：{}。", GsonUtils.toJson(vo), GsonUtils.toJson(response));
        return response;
    }

    @Override
    public AlipayTradeRefundResponse tradeRefund(AliTradeRefundReqVo vo) {
        AlipayConfigAccount alipayConfigAccount = vo.getAlipayConfigAccount();
        AlipayClient alipayClient = initAlipayClient(alipayConfigAccount);

        AlipayTradeRefundRequest alipayRequest = new AlipayTradeRefundRequest();
        // 不需要传过去
        vo.setAlipayConfigAccount(null);
        // 填充业务参数
        alipayRequest.setBizContent(GsonUtils.toJson(vo));
        AlipayTradeRefundResponse response;
        try {
            response = alipayClient.execute(alipayRequest);
        } catch (AlipayApiException e) {
            logger.error("支付宝统一收单交易退款接口调用出错。请求参数：" + GsonUtils.toJson(vo) + "。", e);
            throw new AliException("支付宝统一收单交易退款接口调用出错。请求参数：" + GsonUtils.toJson(vo) + "。", e);
        }

        logger.info("访问支付宝接口【统一收单交易退款接口】。请求参数：{}，晌应：{}。", GsonUtils.toJson(vo), GsonUtils.toJson(response));
        return response;
    }

    @Override
    public AlipayTradeFastpayRefundQueryResponse tradeFastpayRefundQuery(AliTradeFastpayRefundQueryReqVo vo) {
        AlipayConfigAccount alipayConfigAccount = vo.getAlipayConfigAccount();
        AlipayClient alipayClient = initAlipayClient(alipayConfigAccount);

        AlipayTradeFastpayRefundQueryRequest alipayRequest = new AlipayTradeFastpayRefundQueryRequest();
        // 不需要传过去
        vo.setAlipayConfigAccount(null);
        // 填充业务参数
        alipayRequest.setBizContent(GsonUtils.toJson(vo));
        AlipayTradeFastpayRefundQueryResponse response;
        try {
            response = alipayClient.execute(alipayRequest);
        } catch (AlipayApiException e) {
            logger.error("支付宝统一收单交易退款查询接口调用出错。请求参数：" + GsonUtils.toJson(vo) + "。", e);
            throw new AliException("支付宝统一收单交易退款查询接口调用出错。请求参数：" + GsonUtils.toJson(vo) + "。", e);
        }

        return response;
    }

    @Override
    public AliTradeAppPayRespVo tradeAppPay(AliTradeAppPayReqVo vo) {
        AlipayConfigAccount alipayConfigAccount = vo.getAlipayConfigAccount();
        AlipayClient alipayClient = initAlipayClient(alipayConfigAccount);

        AlipayTradeAppPayRequest alipayRequest = new AlipayTradeAppPayRequest();
        // 不需要传过去
        vo.setAlipayConfigAccount(null);
        // 填充业务参数
        alipayRequest.setBizContent(GsonUtils.toJson(vo));
        alipayRequest.setNotifyUrl(alipayConfigAccount.getNotifyUrl());

        AliTradeAppPayRespVo respVo = new AliTradeAppPayRespVo();
        String orderString;
        try {
            // 这里和普通的接口调用不同，使用的是sdkExecute。可参考文档 https://doc.open.alipay.com/docs/doc.htm?spm=a219a.7629140.0.0.GCTtqB&treeId=54&articleId=106370&docType=1
            AlipayTradeAppPayResponse response = alipayClient.sdkExecute(alipayRequest);
            orderString = response.getBody();
        } catch (AlipayApiException e) {
            logger.error("支付宝App支付接口调用出错。请求参数：" + GsonUtils.toJson(vo) + "。", e);
            respVo.setRet(false);
            return respVo;
        }
        respVo.setOrderString(orderString);

        logger.info("访问支付宝接口【App支付接口】。请求参数：{}，晌应：{}。", GsonUtils.toJson(vo), GsonUtils.toJson(respVo));
        return respVo;
    }

    @Override
    public boolean verifySign(Map<String, String> paramsMap, String alipayPublicKey) {
        try {
            return AlipaySignature.rsaCheckV1(paramsMap, alipayPublicKey, "utf-8", paramsMap.get("sign_type"));
        } catch (AlipayApiException e) {
            logger.error("调用支付宝验签SDK时失败。参数alipayPublicKey:" + alipayPublicKey + ",paramsMap：" + GsonUtils.toJson(paramsMap), e);
        }
        return false;
    }

    @Override
    public AlipayFundTransToaccountTransferResponse fundTransToAccountTransfer(AliFundTransToAccountTransferReqVo vo) {
        AlipayConfigAccount alipayConfigAccount = vo.getAlipayConfigAccount();
        AlipayClient alipayClient = initAlipayClient(alipayConfigAccount);

        AlipayFundTransToaccountTransferRequest alipayRequest = new AlipayFundTransToaccountTransferRequest();
        // 不需要传过去
        vo.setAlipayConfigAccount(null);
        // 填充业务参数
        alipayRequest.setBizContent(GsonUtils.toJson(vo));
        AlipayFundTransToaccountTransferResponse response;
        try {
            response = alipayClient.execute(alipayRequest);
        } catch (AlipayApiException e) {
            logger.error("支付宝单笔转账到支付宝账户接口调用出错。请求参数：" + GsonUtils.toJson(vo) + "。", e);
            throw new AliException("支付宝单笔转账到支付宝账户接口调用出错。请求参数：" + GsonUtils.toJson(vo) + "。", e);
        }

        logger.info("访问支付宝接口【单笔转账到支付宝账户接口】。请求参数：{}，晌应：{}。", GsonUtils.toJson(vo), GsonUtils.toJson(response));
        return response;
    }

    @Override
    public AlipayFundTransOrderQueryResponse fundTransOrderQuery(AliFundTransOrderQueryReqVo vo) {
        AlipayConfigAccount alipayConfigAccount = vo.getAlipayConfigAccount();
        AlipayClient alipayClient = initAlipayClient(alipayConfigAccount);

        AlipayFundTransOrderQueryRequest alipayRequest = new AlipayFundTransOrderQueryRequest();
        // 不需要传过去
        vo.setAlipayConfigAccount(null);
        // 填充业务参数
        alipayRequest.setBizContent(GsonUtils.toJson(vo));
        AlipayFundTransOrderQueryResponse response;
        try {
            response = alipayClient.execute(alipayRequest);
        } catch (AlipayApiException e) {
            logger.error("支付宝查询转账订单接口调用出错。请求参数：" + GsonUtils.toJson(vo) + "。", e);
            throw new AliException("支付宝查询转账订单接口调用出错。请求参数：" + GsonUtils.toJson(vo) + "。", e);
        }

        return response;
    }

    @Override
    public AlipayDataDataserviceBillDownloadurlQueryResponse dataDataserviceBillDownloadurlQuery(AliDataDataserviceBillDownloadurlQueryReqVo vo) {
        AlipayConfigAccount alipayConfigAccount = vo.getAlipayConfigAccount();
        AlipayClient alipayClient = initAlipayClient(alipayConfigAccount);

        AlipayDataDataserviceBillDownloadurlQueryRequest alipayRequest = new AlipayDataDataserviceBillDownloadurlQueryRequest();
        // 不需要传过去
        vo.setAlipayConfigAccount(null);
        // 填充业务参数
        alipayRequest.setBizContent(GsonUtils.toJson(vo));
        AlipayDataDataserviceBillDownloadurlQueryResponse response;
        try {
            response = alipayClient.execute(alipayRequest);
        } catch (AlipayApiException e) {
            logger.error("支付宝查询账单下载地址接口调用出错。请求参数：" + GsonUtils.toJson(vo) + "。", e);
            throw new AliException("支付宝查询账单下载地址接口调用出错。请求参数：" + GsonUtils.toJson(vo) + "。", e);
        }

        return response;
    }

    @Override
    public AliBillDataRespVo downloadAndProcessBillData(String billDownloadUrl) {
        if (billDownloadUrl == null) {
            throw new IllegalArgumentException("对账单下载地址不能为空");
        }
        File billZipFile = downloadToTempFile(billDownloadUrl);
        logger.info("支付宝下载对账单产生临时文件。文件路径：" + billZipFile.getAbsolutePath());

        AliBillDataRespVo aliBillDataRespVo = new AliBillDataRespVo();
        try {
            ZipFile zipFile = new ZipFile(billZipFile, Charset.forName("gbk"));;
            ZipInputStream zipInputStream = new ZipInputStream(new FileInputStream(billZipFile),  Charset.forName("gbk"));
            ZipEntry zipEntry;
            List<AliBillDataItem> billData = new ArrayList<>();
            List<String> fileNames = new ArrayList<>();
            while ((zipEntry = zipInputStream.getNextEntry()) != null) {
                if (zipEntry.isDirectory()) {
                    // do nothing
                } else {
                    String fileName = zipEntry.getName();
                    if (!fileName.contains("业务明细") || fileName.contains("汇总")) {
                        continue;
                    }
                    fileNames.add(fileName);
                    logger.info("正在处理支付宝下载对账单中的文件：" + fileName);
                    List<AliBillDataItem> billDataTemp = processBillCSVFile(zipFile, zipEntry);
                    logger.info("已成功处理支付宝下载对账单中的文件：{}。从该账单文件已读取账单条数：{}条。", fileName, billDataTemp.size());
                    billData.addAll(billDataTemp);
                }
            }
            logger.info("已成功分别从下列文件{}，共读取账单总条数：{}条。", fileNames, billData.size());
            aliBillDataRespVo.setBillData(billData);
            zipInputStream.close();
            zipFile.close();
            boolean isDelete = billZipFile.delete();// 删除临时文件
            logger.info("删除支付宝下载对账单产生临时文件是否成功：" + isDelete + "。文件路径：" + billZipFile.getAbsolutePath());
        } catch (Exception e) {
            logger.error("支付宝下载并解析对账单调用出错。请求参数：billDownloadUrl=" + billDownloadUrl + "。", e);
            throw new AliException("支付宝下载并解析对账单调用出错。请求参数：billDownloadUrl=" + billDownloadUrl + "。", e);
        }

        return aliBillDataRespVo;
    }

    @Override
    public AliBillDataRespVo downloadAndProcessBillData(AliDataDataserviceBillDownloadurlQueryReqVo vo) {
        AlipayDataDataserviceBillDownloadurlQueryResponse alipayDataDataserviceBillDownloadurlQueryResponse = dataDataserviceBillDownloadurlQuery(vo);
        String billDownloadUrl = alipayDataDataserviceBillDownloadurlQueryResponse.getBillDownloadUrl();
        if (billDownloadUrl == null) {
            logger.warn("对账单下载地址为空，可能是没有账单下载");
            AliBillDataRespVo res = new AliBillDataRespVo();
            res.setBillData(new ArrayList<AliBillDataItem>());
            return res;
        }
        return downloadAndProcessBillData(billDownloadUrl);
    }

    private List<AliBillDataItem> processBillCSVFile(ZipFile zipFile, ZipEntry zipEntry) throws IOException {
        List<AliBillDataItem> billData = new ArrayList<>();
        Reader in = new InputStreamReader(zipFile.getInputStream(zipEntry), "gbk");
        Iterable<CSVRecord> records = CSVFormat.EXCEL
                .withHeader("支付宝交易号", "商户订单号", "业务类型", "商品名称", "创建时间", "完成时间", "门店编号", "门店名称", "操作员", "终端号", "对方账户", "订单金额（元）", "商家实收（元）", "支付宝红包（元）", "集分宝（元）", "支付宝优惠（元）", "商家优惠（元）", "券核销金额（元）", "券名称", "商家红包消费金额（元）", "卡消费金额（元）", "退款批次号/请求号", "服务费（元）", "分润（元）", "备注")
                .parse(in);
        for (CSVRecord record : records) {
            // 表头
            if (record.getRecordNumber() <= 5) {
                continue;
            }
            // 表尾
            if (record.get(0).contains("业务明细列表结束")) {
                break;
            }
            // 账单数据
            AliBillDataItem billDataItem = new AliBillDataItem();
            billDataItem.setTradeNo(trim(record.get("支付宝交易号")));
            billDataItem.setOutTradeNo(trim(record.get("商户订单号")));
            String tradeTypeStr = trim(record.get("业务类型"));
            billDataItem.setTradeType(AliTradeTypeEnum.getAliTradeTypeEnumByDesc(tradeTypeStr));
            billDataItem.setSubject(trim(record.get("商品名称")));
            String createTimeStr = trim(record.get("创建时间"));
            billDataItem.setCreateTime(TimeUtils.engStringToDate(createTimeStr, TimeUtils.DF_PATTERN_YYYY_MM_DD_HH_MM_SS));
            String finishTimeStr = trim(record.get("完成时间"));
            billDataItem.setFinishTime(TimeUtils.engStringToDate(finishTimeStr, TimeUtils.DF_PATTERN_YYYY_MM_DD_HH_MM_SS));
            billDataItem.setStoreId(trim(record.get("门店编号")));
            billDataItem.setStoreName(trim(record.get("门店名称")));
            billDataItem.setOperator(trim(record.get("操作员")));
            billDataItem.setTerminalId(trim(record.get("终端号")));
            billDataItem.setBuyerAccount(trim(record.get("对方账户")));
            billDataItem.setTotalAmount(new BigDecimal(trim(record.get("订单金额（元）"))));
            billDataItem.setReceiptAmount(new BigDecimal(trim(record.get("商家实收（元）"))));
            billDataItem.setAliRedPacketAmount(new BigDecimal(trim(record.get("支付宝红包（元）"))));
            billDataItem.setPointAmount(new BigDecimal(trim(record.get("集分宝（元）"))));
            billDataItem.setAliDiscountAmount(new BigDecimal(trim(record.get("支付宝优惠（元）"))));
            billDataItem.setCouponAmount(new BigDecimal(trim(record.get("券核销金额（元）"))));
            billDataItem.setCouponName(trim(record.get("券名称")));
            billDataItem.setBusinessRedPacketAmount(new BigDecimal(trim(record.get("商家红包消费金额（元）"))));
            billDataItem.setCardAmount(new BigDecimal(trim(record.get("卡消费金额（元）"))));
            billDataItem.setOutRequestNo(trim(record.get("退款批次号/请求号")));
            billDataItem.setServiceFee(new BigDecimal(trim(record.get("服务费（元）"))));
            billDataItem.setSplitFee(new BigDecimal(trim(record.get("分润（元）"))));
            billDataItem.setRemark(trim(record.get("备注")));

            billData.add(billDataItem);
        }
        return billData;
    }

    private String trim (String str) {
        if (str == null) {
            return str;
        }
        return str.trim();
    }

    private File downloadToTempFile(String billDownloadUrl) {
        File billZipFile = null;
        try {
            URL u = new URL(billDownloadUrl);
            billZipFile = File.createTempFile("ALIPAY_", ".zip");
            FileUtils.copyURLToFile(u, billZipFile);
        } catch (Exception e) {
            logger.error("下载对账单文件出错。可能原因：1、下载链接已过时，下载连接只有30s的有效时间;2、其它未知原因。billDownloadUrl=" + billDownloadUrl, e);
            throw new AliException("下载对账单文件出错。可能原因：1、下载链接已过时，下载连接只有30s的有效时间;2、其它未知原因。billDownloadUrl=" + billDownloadUrl + "。", e);
        }
        return billZipFile;
    }

    @Override
    public AlipayClient initAlipayClient(AlipayConfigAccount alipayConfigAccount) {
        String url = alipayConfigAccount.getUrl();
        String format = alipayConfigAccount.getFormat();
        String charSet = alipayConfigAccount.getCharSet();
        String signType = alipayConfigAccount.getSignType();
        String appId = alipayConfigAccount.getAppId();
        String appPrivateKey = alipayConfigAccount.getAppPrivateKey();
        String alipayPublicKey = alipayConfigAccount.getAlipayPublicKey();
        AlipayClient alipayClient;

        if (appIdToAliPayAccountMap.containsKey(appId)) {
            alipayClient = appIdToAliPayAccountMap.get(appId);
        } else {
            // alipayClient可以共用，加入缓存策略，提升速度
            alipayClient = new DefaultAlipayClient(url, appId, appPrivateKey, format, charSet, alipayPublicKey, signType);
            appIdToAliPayAccountMap.put(appId, alipayClient);
        }

        return alipayClient;
    }

}
